8 simple ways how to boost your coding skills (not just) in R

八招提升你的 R 语言编程能力

Introdction 引言

Our world is generating more and more data, which people and businesses want to turn into something useful. This naturally attracts many data scientists – or sometimes called data analysts, data miners, and many other fancier names – who aim to help with this extraction of information from data.

这个世界每天都在源源不断地生产数据,而人们尤其是商界往往希望从这些数据中获取到有价值的信息。而这一点也促使很多试图从数据中提取有用信息的数据科学家们(或被叫做数据分析师、数据挖掘者等等听起来不错的称谓)不断地进行探索。

A lot of data scientists around me graduated in statistics, mathematics, physics or biology. During their studies they focused on individual modelling techniques or nice visualizations for the papers they wrote. Nobody had ever taken a proper computer science course that would help them tame the programming language completely and allow them to produce a nice and professional code that is easy to read, can be re-used, runs fast and with reasonable memory requirements, is easy to collaborate on and most importantly gives reliable results.

很多作者身边的数据科学家毕业于统计学、数学、物理学或生物学专业。他们在研究过程中,往往只关注于独立的模型方法或者漂亮的可视化效果,却没人尝试通过学习计算机科学的相关课程提高自身掌握编程语言的能力,帮助他们敲出更优化和专业的代码——具有良好的易读性,可重复使用,运行高效,内存占用合理,容易移植,最重要的是可以产出可信的结果。

I am no exception to this. During my studies we used R and Matlab to get a hands-on experience with various machine learning techniques. We obviously focused on choosing the best model, tuning its parameters, solving for violated model assumptions and other rather theoretical concepts. So when I started my professional career I had to learn how to deal with imperfect input data, how to create a script that can run daily, how to fit the best model and store a predictions in a database. Or even to use them directly in some online client facing point.

作者也不外如是。他在研究期间,曾有过使用 R 语言和Matlab 用于大量机器学习算法的实践经历。实践中,我们总是在注意力放在选取最优的模型,调整参数,解决与违背模型假设相关的问题和其他理论概念等等方面上。因此,作者在工作之初不得不去学习如何处理有缺陷的输入数据,写出每天都能够正常运行的代码,拟合最优的模型,以数据集的方式存储预测结果,或是直接使用它们解决在线客户的问题。

To do this I took the standard path. Reading books, papers, blogs, trying new stuff working on hobby projects, googling, stack-overflowing and asking colleagues. But again mainly focusing on overcoming small ad hoc problems.

为了顺利完成工作,作者踏上码农标准的学习之路:阅读书籍、论文、博客,对感兴趣的项目尝试新方法去处理,谷歌搜索问题,在 stack-overflow 网站上提问,寻求的同事的帮助。但总是忙于解决一些微小且特定的问题。

Luckily for me, I’ve met a few smart computer scientists on the way who showed me how to develop code that is more professional. Or at least less amateurish. What follows is a list of the most important points I had to learn since I left the university. These points allowed me to work on more complex problems both theoretically and technically. I must admit that making your coding skills better is a never ending story that restarts with every new project.

幸运的是,作者曾与一些“机智”的计算科学家讨论过如何提高编程能力,写出专业化(至少别太业余)代码的问题。下文是作者自离校之后感悟到的一些关键点。这些关键点帮助作者在理论和实践中解决了更为复杂的问题。提高你的编程能力是一条学而无涯的路,你的每个新项目都是一次锻炼的过程。

1. Parameters, constants and functions 参数、常量和函数

You are able to easily re-use your code if you make it applicable to similar problems as well. A simple wisdom that is however quite tricky to apply in practice. Your building blocks here are parameters, constants and functions.

如果你在写代码的时候就已经考虑过用它来解决相似的问题,那么写出来的代码是很容易被移植的。这个想法虽然简单,实践起来却并不容易。而参数、常量和函数便是你实现这点的基础。

Parameters enable you to change important variables and settings in one place. You should never have anything hard-coded in the body of your code. Constants help you to define static variables that cannot be altered. Constants are useful for example when you need to compare strings.

参数可用于改变重要变量的值和完成一些设置。写代码的时候,注意不要以常量的形式去设定参数。而常量则可以用来定义不会改变的静态量,举例来说,当你需要比较字符串时就可以用到常量。

install.packages("futile.logger")
library(caret)
library(futile.logger)

#' constants常量
DATASET_IRIS <- 'iris'
DATASET_MTCARS <- 'mtcars'
IRIS_TARGET <- 'Sepal.Length'
MTCARS_TARGET <- 'mpg'
MODELLING_METHOD_RF <- 'random forest'
MODELLING_METHOD_GBM <- 'gradient boosting machine'

#' parameters 参数
DATASET <- DATASET_IRIS
MODELLING_METHOD <- MODELLING_METHOD_GBM

#' load data 载入数据
flog.info(paste0('Loading ', DATASET, ' dataset'))
if (DATASET == DATASET_IRIS){
  data(iris)
  df <- iris
  target_variable <- IRIS_TARGET
} else if (DATASET == DATASET_MTCARS){
  data(mtcars)
  df <- mtcars
  target_variable <- MTCARS_TARGET
}

#' create formula 构建表达式
modelling_formula <- as.formula(paste0(target_variable, '~.'))

#' train model
flog.info(paste0('Fitting ', MODELLING_METHOD))
if(MODELLING_METHOD == MODELLING_METHOD_RF){
  set.seed(42)
  my_model <- caret::train(form=modelling_formula,
                           data=df,
                           method='rf')
} else if(MODELLING_METHOD == MODELLING_METHOD_GBM){
  set.seed(42)
  my_model <- caret::train(form=modelling_formula,
                           data=df,
                           method='gbm',
                           verbose=FALSE)
}
my_model

Functions are key ingredients of programming. Always put the repetitive tasks in your code into functions. These functions should always aim to perform one task and be general enough to be used for similar cases. How general typically depends on what you want to achieve.

函数是编程中的重要一环,我们往往以函数的方式处理重复性工作。通常情况下,函数应当能够解决某种特定的问题并足以应用于相似的情况,其被推广的程度取决于你想实现什么样的目标。

Even helper functions should be well documented. The absolute minimum is to summarize what the function should do and what is the meaning of input parameters. I usually use roxygen comments so that the function can be later used in an R package without much extra work. For more details please see here:http://r-pkgs.had.co.nz/man.html.

函数需要附上解释说明部分,最起码要说明函数可以解决什么问题和各个输入参数的意义。作者一般使用 R 包构建时的注释方式,以便于函数可以直接用于 R 包中,而不需要额外的处理。更多注释的细节可在 ”如何编写 R 包的说明文档“ 网页中查看。

#' Calculates Root Mean Squeared Error 计算均方根误差
#'
#' @param observed vector with observed values 参数表示观测值
#' @param predicted vector with predicted values 参数表示预测值
#' @return numeric 返回数值类型
f_calculate_rmse <- function(observed, predicted){
  error <- observed - predicted
  return(round(sqrt(mean(error^2)), 2))
}

You have to test the functions you are writing anyway so it is a good idea to automate this step in case you would like to update the functions in the future. This is important especially if you plan to wrap you functions in a package: http://r-pkgs.had.co.nz/. Nice way to do this is using testthat:https://github.com/hadley/testthat package. Here:http://kbroman.org/pkg_primer/pages/tests.html is a nice page how to run your tests automatically.

你需要检验所编写的函数是否能够正常运行,因此,将检验的流程自动化处理会便于日后对函数的改动。当你需要 构建 R 包 时创建函数,这点尤其重要。testthat 包可以很方便的解决这个问题,你可以在 “如何检验 R 包中代码的有效性” 网页中看到如何自动检验你的代码。

library(testthat)
library(Metrics)

#' testing of f_calculate_rmse 检验 f_calculate_rmse 函数
test_that('Root Mean Square Error', {
  #' create some data
  n <- 100
  observed <- rnorm(n)
  predicted <- rnorm(n)
  my_rmse <- f_calculate_rmse(observed=observed, predicted=predicted)

  #' same results as Metrics::rmse  检验与 Metrics 包中的 rmse 函数结果是否相同 
  expect_equal(my_rmse, 
               Metrics::rmse(actual=observed, predicted=predicted), 
               tolerance=.05) 
  #' output is numeric and non-negative 检验输出结果是数值类型且非负数
  expect_that(my_rmse, is_a("numeric")) 
  expect_that(my_rmse >= 0, is_true())
})

Obviously one does not need to write all the functions needed. A great advantage of R is that there are so many functions available in thousands of available libraries. To make sure you will not run into namespace problems when two loaded libraries both contain a function with the same name, specify the package you want to use by packagename::functionname(). An example is the summarise function when both plyr:https://cran.r-project.org/web/packages/plyr/index.html and dplyr: https://cran.r-project.org/web/packages/dplyr/index.html packages are loaded.

好在我们不需要自己编写所有的函数, R 语言的好处就在于在它大量的包里已经有很多可用的函数了。为了避免出现载入的两个包中有相同名称函数的重名问题,你可以通过 packagename::functionname() 的方式指定想使用的包名。比如说,在载入的 plyrdplyr 包中都含有summarise函数。

library(plyr)
library(dplyr)

#' load data 载入数据
data(mtcars)

#' see what happens 查看结果
summarise(group_by(mtcars, cyl), n=n())
plyr::summarise(group_by(mtcars, cyl), n=n())
dplyr::summarise(group_by(mtcars, cyl), n=n())

2. Style 样式

You will be reading your code again in the future so be nice to yourself (and anyone else who will have to read it) and have a consistent coding style. A lot of people use Google’s R style: https://google.github.io/styleguide/Rguide.xml or Hadley Wickham’s style: http://adv-r.had.co.nz/Style.html.

你以后还是需要重新阅读你自己的代码,因此,代码需要保持一个连贯的样式,便于自己和其他阅读的人能够理解。很多人使用 谷歌的 R 编程规范 或是 Hadley Wickham 的 R 语言编程样式。

Here I also need to stress that it is important to comment your code. Especially when you consider your solution brilliant and obvious. Also please do not be afraid of long but self-explanatory function and variable names.

给代码加上注释是非常重要的,特别是在你认为构建思路很好的时候方便日后查看。同时,也不要忘了加上看似有些冗长的但是能够自我解释性的函数和变量名。

3. Version control 版本控制

Always use version control for your projects. It will save you a lot of nerves. It has so many advantages. Here: http://stackoverflow.com/questions/1408450/why-should-i-use-version-controlis a nice summary of them. The most important to me are:

项目管理中,版本控制也很重要,它可以节省你大量的精力。版本控制具有很多好处,你可以在 “使用版本控制的原因” 网页中看到。对于作者而言,最重要的好处在于:

  • ability to revert back to previous versions of my code 能够回溯到之前版本的代码

  • clean project folder because I can delete anything without fear 管理项目文件,不用担心误操作

  • easy to invite colleagues to collaborate on the project 便于与同事协作

Using git is easy: http://rogerdudler.github.io/git-guide/. Especially from RStudio: https://support.rstudio.com/hc/en-us/articles/200532077-Version-Control-with-Git-and-SVN.

你可以使用 git 进行版本控制,特别是在 RStudio 中。

4. Development 优化开发代码

Development doesn’t necessarily need to be a messy process.

优化代码的过程不一定就是杂乱无章的。

Your code is not working? Then you need to be able to quickly locate the problem and fix it. Luckily, RStudio has a lot of built-in debugging tools: https://support.rstudio.com/hc/en-us/articles/205612627-Debugging-with-RStudio so that you can stop the code at the point where you suspect the problem is arising, and look at and/or walk through the code, step-by-step at that point.

如果代码运行出故障,就需要快速定位到问题并解决它。好在 RStudio 拥有很多 内置调试工具 ,你可以在认为代码出现问题的地方创建一个断点,然后重新运行代码,重复这个过程来找出问题所在。

#' create some data 创建数据
set.seed(42)
n <- 100
observed <- rnorm(n)
predicted <- rnorm(n)

#' debug the function 调试函数
debug(f_calculate_rmse)
f_calculate_rmse(observed=observed, predicted=predicted)

Each programming language has its own strengths and weaknesses that you need to keep in mind. You don’t want your code to run too slow or use too much memory. A handy tool for this is profiling. Again, RStudio comes with a solution: https://support.rstudio.com/hc/en-us/articles/218221837-Profiling-with-RStudio to this. Profiling enables you to detect where the execution of you code lasts the longest and where it uses the most memory. Do not rely on your intuition when optimizing your code!

你需要知道,每种编程语言都有它的优点和不足。你也不希望代码运行得太慢或者占用太多内存。分析代码的结构可以帮助解决这个问题,而 RStudio 也提供了这方面的 解决方案 。分析代码结构能够监测到运行代码最耗时和最占用内存的地方。在优化代码的时候不要过度依赖自己的直觉。

You should also check how the running time and memory requirements increase with the size of data. This will give you an idea for what data can be your code used and what could be the consequences for scaling.

在数据集增大时,你需要检查其对于运行时间和内存占用需求的影响,这点有助于你理解应当选择什么样的数据集和在数据集规模改变时会引发什么样的后果。

library(profvis)

#' profiling of f_calculate_rmse 分析 f_calculate_rmse 函数结构
profvis({
  set.seed(42)
  n <- 1e5
  observed <- rnorm(n)
  predicted <- rnorm(n)
  f_calculate_rmse(observed=observed, predicted=predicted)
})

During the development you will encounter many problems. Each time this happens you should improve the error handling in your code and raise a self-explanatory warning or error. Especially mind the data types and missing parameters.

代码优化过程中,你会面对很多问题。每次问题出现,尤其需要关注数据类型和遗漏的参数,你就应当能够从中提升解决代码错误的能力,并认识到这种警告或错误的成因。

5. Deployment 部署

Deployment means that your code will need to run automatically. Or at least without you executing it line by line. In this case it is very helpful to know what is going on and whether the execution went well without any problems. For this purpose I use futile.logger: https://cran.r-project.org/web/packages/futile.logger/index.html package. It is a light solution and enables me to log the execution of my codes both to screen or file. I just need to write understandable messages in the correct places in my code.

部署意味着你的代码需要实现自动运行的功能,至少不需要你的干预,代码就可以一行行运行下去。明确了解代码输出的内容和运行是否顺利是非常重要的。futile.logger 包可以实现代码的部署,它是一个轻量级解决方案,并且能够通过屏幕输出或导出文件记录下代码的运行过程。你所要做的仅仅是在合适的位置写下便于理解的相关信息。

library(futile.logger)

#' logging setup 建立记录器
flog.threshold(DEBUG) # level of logging
flog.appender(appender.file('foo.log')) # log to file

#' logging 记录器的相关信息
flog.info('Some info message')
flog.debug('Some debug message')
flog.warn('Some warning message')
flog.error('Some error message')

Automated code execution is typically done by Cron scheduler using Rscript foo.r. This command runs the foo.r code. Very often you want to specify some parameters of the script so that you can analyse different data, specify which machine learning method to use, if you want to retrain the model and so on. For this I use the argparse: https://cran.r-project.org/web/packages/argparse/index.html package. Following code enables my to specify the csv with input data in command line: Rscript my_code.r -if latest_data.csv.

代码的自动运行通常可以使用 Cron 计划任务程序运行 foo.r 的 R 语言脚本。这个命令将会运行 foo.r 代码。但往往你需要设定这个脚本里的一些参数来分析不同的数据,如果想重新训练模型的话还需要指定想使用的机器学习方法。因此,作者使用 argparse 包,下列代码可以输入命令Rscript my_code.r -if latest_data.csv实现通过输入文件名指定读入的 csv 文件:

library(argparse)

#' default parameters 设定默认参数
INPUT_FILE_DEFAULT <- 'input.csv'

#' create parser object 创建解析器
parser <- ArgumentParser(description='My code')

#' define arguments 定义参数类目
parser$add_argument('-if', '--input_file', 
                    default=INPUT_FILE_DEFAULT, 
                    type='character', 
                    help='Location of csv file with input data')

#' get command line options 传入命令行
args <- parser$parse_args()

#' load data 载入数据
data <- read.csv(args$input_file)

6. Plotting 绘图

Data visualization is the “shop window” of analytics. Therefore you will probably spend a lot of time fine-tuning each plot. Good best practice is the following.

数据可视化可以称得上是数据分析中的“橱窗柜”了,因此你可能会花费大量时间调整每个输出图。最好的操作方案如下:

  1. define style, color palette and any other parameters in a separate script 在每个脚本中,定义样式,调色板和其他参数;

  2. write a function to create a plot object 编写输出图的函数;

  3. use another function to either show the plot or save it to a file 使用另一个函数展示输出图或保存成文件。

Let’s see how it works in the following basic example.

下面是一个基础的实例:

library(ggplot2)
data(iris)

#' some basic style 设定基本样式
my_collors <- list('red'='#B22222')

#' function to create histogram 编写创建直方图的函数
f_create_histogram <- function(df, column){
  
  p <- ggplot(df, aes_string(x=column)) +
    geom_histogram(binwidth=.1, fill=my_collors$red) +
    ggtitle(paste0('Histogram of ', column))
  
  return(p)
}

#' create plots  创建输出图对象
sepal_length_hist <- f_create_histogram(iris, 'Sepal.Length')
sepal_width_hist <- f_create_histogram(iris, 'Sepal.Width')

#' show 展示
sepal_length_hist
#' save 保存
ggsave('sepal_width_hist.png', plot=sepal_width_hist)

7. Reproducibility 复现性

Make sure your code is reproducible. Because a lot of data science steps involve random sampling or optimization, we need to make sure that we can repeat the code with the same results. That is why it is critical to use set.seed() function.

确保你的代码可以被复现。由于很多数据分析的步骤中包含随机的取样或优化,因此我们需要确保重新运行代码可以得到同样的结果。这也是为什么我们需要使用set.seed()函数。

set.seed(42); sample(LETTERS, 5)
# [1] "X" "Z" "G" "T" "O"
set.seed(42); sample(LETTERS, 5)
# [1] "X" "Z" "G" "T" "O"
sample(LETTERS, 5)
# [1] "N" "S" "D" "P" "W"

8. Combine tools 组合工具

Once you become confident in R programming you tend to do everything in R. Please do not forget that there are many other tools available and thanks to connectors they can be used together with R. For example I very often combine R with Python or SQL databases.

一旦你在 R 语言编程中小有所成,你会希望尽可能多地使用 R 中的工具。但请别忘了还有很多其他的可用工具,而且通过接口,它们可以与 R 实现共通。比方说,作者经常将 R 语言和 Python 或 SQL 数据库一起使用。

9. Reference and Tutorials

Tutorials

There are so many tutorials around that it’s hard to pick one. I personally find these books very useful:

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2stOCBzaW1wbGUgd2F5cyBob3cgdG8gYm9vc3QgeW91ciBjb2Rpbmcgc2tpbGxzIChub3QganVzdCkgaW4gUiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KIyA4IHNpbXBsZSB3YXlzIGhvdyB0byBib29zdCB5b3VyIGNvZGluZyBza2lsbHMgKG5vdCBqdXN0KSBpbiBSIA0KDQo+IFvlhavmi5vmj5DljYfkvaDnmoQgUiDor63oqIDnvJbnqIvog73liptdKGh0dHBzOi8vYmxvZy5hbG9va2FuYWx5dGljcy5jb20vMjAxNy8wMy8wOC84LXNpbXBsZS13YXlzLWhvdy10by1ib29zdC15b3VyLWNvZGluZy1za2lsbHMtbm90LWp1c3QtaW4tci8pDQoNCg0KIyMgSW50cm9kY3Rpb24g5byV6KiADQoNCk91ciB3b3JsZCBpcyBnZW5lcmF0aW5nIG1vcmUgYW5kIG1vcmUgZGF0YSwgd2hpY2ggcGVvcGxlIGFuZCBidXNpbmVzc2VzIHdhbnQgdG8gdHVybiBpbnRvIHNvbWV0aGluZyB1c2VmdWwuIFRoaXMgbmF0dXJhbGx5IGF0dHJhY3RzIG1hbnkgZGF0YSBzY2llbnRpc3RzIOKAkyBvciBzb21ldGltZXMgY2FsbGVkIGRhdGEgYW5hbHlzdHMsIGRhdGEgbWluZXJzLCBhbmQgbWFueSBvdGhlciBmYW5jaWVyIG5hbWVzIOKAkyB3aG8gYWltIHRvIGhlbHAgd2l0aCB0aGlzIGV4dHJhY3Rpb24gb2YgaW5mb3JtYXRpb24gZnJvbSBkYXRhLg0KDQrov5nkuKrkuJbnlYzmr4/lpKnpg73lnKjmupDmupDkuI3mlq3lnLDnlJ/kuqfmlbDmja7vvIzogIzkurrku6zlsKTlhbbmmK/llYbnlYzlvoDlvoDluIzmnJvku47ov5nkupvmlbDmja7kuK3ojrflj5bliLDmnInku7flgLznmoTkv6Hmga/jgILogIzov5nkuIDngrnkuZ/kv4Pkvb/lvojlpJror5Xlm77ku47mlbDmja7kuK3mj5Dlj5bmnInnlKjkv6Hmga/nmoTmlbDmja7np5Hlrablrrbku6zvvIjmiJbooqvlj6vlgZrmlbDmja7liIbmnpDluIjjgIHmlbDmja7mjJbmjpjogIXnrYnnrYnlkKzotbfmnaXkuI3plJnnmoTnp7DosJPvvInkuI3mlq3lnLDov5vooYzmjqLntKLjgIINCg0KQSBsb3Qgb2YgZGF0YSBzY2llbnRpc3RzIGFyb3VuZCBtZSBncmFkdWF0ZWQgaW4gc3RhdGlzdGljcywgbWF0aGVtYXRpY3MsIHBoeXNpY3Mgb3IgYmlvbG9neS4gRHVyaW5nIHRoZWlyIHN0dWRpZXMgdGhleSBmb2N1c2VkIG9uIGluZGl2aWR1YWwgbW9kZWxsaW5nIHRlY2huaXF1ZXMgb3IgbmljZSB2aXN1YWxpemF0aW9ucyBmb3IgdGhlIHBhcGVycyB0aGV5IHdyb3RlLiBOb2JvZHkgaGFkIGV2ZXIgdGFrZW4gYSBwcm9wZXIgY29tcHV0ZXIgc2NpZW5jZSBjb3Vyc2UgdGhhdCB3b3VsZCBoZWxwIHRoZW0gdGFtZSB0aGUgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgY29tcGxldGVseSBhbmQgYWxsb3cgdGhlbSB0byBwcm9kdWNlIGEgbmljZSBhbmQgcHJvZmVzc2lvbmFsIGNvZGUgdGhhdCBpcyBlYXN5IHRvIHJlYWQsIGNhbiBiZSByZS11c2VkLCBydW5zIGZhc3QgYW5kIHdpdGggcmVhc29uYWJsZSBtZW1vcnkgcmVxdWlyZW1lbnRzLCBpcyBlYXN5IHRvIGNvbGxhYm9yYXRlIG9uIGFuZCBtb3N0IGltcG9ydGFudGx5IGdpdmVzIHJlbGlhYmxlIHJlc3VsdHMuDQoNCuW+iOWkmuS9nOiAhei6q+i+ueeahOaVsOaNruenkeWtpuWutuavleS4muS6jue7n+iuoeWtpuOAgeaVsOWtpuOAgeeJqeeQhuWtpuaIlueUn+eJqeWtpuS4k+S4muOAguS7luS7rOWcqOeglOeptui/h+eoi+S4re+8jOW+gOW+gOWPquWFs+azqOS6jueLrOeri+eahOaooeWei+aWueazleaIluiAhea8guS6rueahOWPr+inhuWMluaViOaenO+8jOWNtOayoeS6uuWwneivlemAmui/h+WtpuS5oOiuoeeul+acuuenkeWtpueahOebuOWFs+ivvueoi+aPkOmrmOiHqui6q+aOjOaPoee8lueoi+ivreiogOeahOiDveWKm++8jOW4ruWKqeS7luS7rOaVsuWHuuabtOS8mOWMluWSjOS4k+S4mueahOS7o+eggeKAlOKAlOWFt+acieiJr+WlveeahOaYk+ivu+aAp++8jOWPr+mHjeWkjeS9v+eUqO+8jOi/kOihjOmrmOaViO+8jOWGheWtmOWNoOeUqOWQiOeQhu+8jOWuueaYk+enu+akje+8jOacgOmHjeimgeeahOaYr+WPr+S7peS6p+WHuuWPr+S/oeeahOe7k+aenOOAgg0KDQpJIGFtIG5vIGV4Y2VwdGlvbiB0byB0aGlzLiBEdXJpbmcgbXkgc3R1ZGllcyB3ZSB1c2VkIFIgYW5kIE1hdGxhYiB0byBnZXQgYSBoYW5kcy1vbiBleHBlcmllbmNlIHdpdGggdmFyaW91cyBtYWNoaW5lIGxlYXJuaW5nIHRlY2huaXF1ZXMuIFdlIG9idmlvdXNseSBmb2N1c2VkIG9uIGNob29zaW5nIHRoZSBiZXN0IG1vZGVsLCB0dW5pbmcgaXRzIHBhcmFtZXRlcnMsIHNvbHZpbmcgZm9yIHZpb2xhdGVkIG1vZGVsIGFzc3VtcHRpb25zIGFuZCBvdGhlciByYXRoZXIgdGhlb3JldGljYWwgY29uY2VwdHMuIFNvIHdoZW4gSSBzdGFydGVkIG15IHByb2Zlc3Npb25hbCBjYXJlZXIgSSBoYWQgdG8gbGVhcm4gaG93IHRvIGRlYWwgd2l0aCBpbXBlcmZlY3QgaW5wdXQgZGF0YSwgaG93IHRvIGNyZWF0ZSBhIHNjcmlwdCB0aGF0IGNhbiBydW4gZGFpbHksIGhvdyB0byBmaXQgdGhlIGJlc3QgbW9kZWwgYW5kIHN0b3JlIGEgcHJlZGljdGlvbnMgaW4gYSBkYXRhYmFzZS4gT3IgZXZlbiB0byB1c2UgdGhlbSBkaXJlY3RseSBpbiBzb21lIG9ubGluZSBjbGllbnQgZmFjaW5nIHBvaW50Lg0KDQrkvZzogIXkuZ/kuI3lpJblpoLmmK/jgILku5blnKjnoJTnqbbmnJ/pl7TvvIzmm77mnInov4fkvb/nlKggUiDor63oqIDlkoxNYXRsYWIg55So5LqO5aSn6YeP5py65Zmo5a2m5Lmg566X5rOV55qE5a6e6Le157uP5Y6G44CC5a6e6Le15Lit77yM5oiR5Lus5oC75piv5Zyo5rOo5oSP5Yqb5pS+5Zyo6YCJ5Y+W5pyA5LyY55qE5qih5Z6L77yM6LCD5pW05Y+C5pWw77yM6Kej5Yaz5LiO6L+d6IOM5qih5Z6L5YGH6K6+55u45YWz55qE6Zeu6aKY5ZKM5YW25LuW55CG6K665qaC5b+1562J562J5pa56Z2i5LiK44CC5Zug5q2k77yM5L2c6ICF5Zyo5bel5L2c5LmL5Yid5LiN5b6X5LiN5Y675a2m5Lmg5aaC5L2V5aSE55CG5pyJ57y66Zm355qE6L6T5YWl5pWw5o2u77yM5YaZ5Ye65q+P5aSp6YO96IO95aSf5q2j5bi46L+Q6KGM55qE5Luj56CB77yM5ouf5ZCI5pyA5LyY55qE5qih5Z6L77yM5Lul5pWw5o2u6ZuG55qE5pa55byP5a2Y5YKo6aKE5rWL57uT5p6c77yM5oiW5piv55u05o6l5L2/55So5a6D5Lus6Kej5Yaz5Zyo57q/5a6i5oi355qE6Zeu6aKY44CCDQoNClRvIGRvIHRoaXMgSSB0b29rIHRoZSBzdGFuZGFyZCBwYXRoLiBSZWFkaW5nIGJvb2tzLCBwYXBlcnMsIGJsb2dzLCB0cnlpbmcgbmV3IHN0dWZmIHdvcmtpbmcgb24gaG9iYnkgcHJvamVjdHMsIGdvb2dsaW5nLCBzdGFjay1vdmVyZmxvd2luZyBhbmQgYXNraW5nIGNvbGxlYWd1ZXMuIEJ1dCBhZ2FpbiBtYWlubHkgZm9jdXNpbmcgb24gb3ZlcmNvbWluZyBzbWFsbCBhZCBob2MgcHJvYmxlbXMuDQoNCuS4uuS6humhuuWIqeWujOaIkOW3peS9nO+8jOS9nOiAhei4j+S4iueggeWGnOagh+WHhueahOWtpuS5oOS5i+i3r++8mumYheivu+S5puexjeOAgeiuuuaWh+OAgeWNmuWuou+8jOWvueaEn+WFtOi2o+eahOmhueebruWwneivleaWsOaWueazleWOu+WkhOeQhu+8jOiwt+atjOaQnOe0oumXrumimO+8jOWcqCBzdGFjay1vdmVyZmxvdyDnvZHnq5nkuIrmj5Dpl67vvIzlr7vmsYLnmoTlkIzkuovnmoTluK7liqnjgILkvYbmgLvmmK/lv5nkuo7op6PlhrPkuIDkupvlvq7lsI/kuJTnibnlrprnmoTpl67popjjgIINCg0KTHVja2lseSBmb3IgbWUsIEnigJl2ZSBtZXQgYSBmZXcgc21hcnQgY29tcHV0ZXIgc2NpZW50aXN0cyBvbiB0aGUgd2F5IHdobyBzaG93ZWQgbWUgaG93IHRvIGRldmVsb3AgY29kZSB0aGF0IGlzIG1vcmUgcHJvZmVzc2lvbmFsLiBPciBhdCBsZWFzdCBsZXNzIGFtYXRldXJpc2guIFdoYXQgZm9sbG93cyBpcyBhIGxpc3Qgb2YgdGhlIG1vc3QgaW1wb3J0YW50IHBvaW50cyBJIGhhZCB0byBsZWFybiBzaW5jZSBJIGxlZnQgdGhlIHVuaXZlcnNpdHkuIFRoZXNlIHBvaW50cyBhbGxvd2VkIG1lIHRvIHdvcmsgb24gbW9yZSBjb21wbGV4IHByb2JsZW1zIGJvdGggdGhlb3JldGljYWxseSBhbmQgdGVjaG5pY2FsbHkuIEkgbXVzdCBhZG1pdCB0aGF0IG1ha2luZyB5b3VyIGNvZGluZyBza2lsbHMgYmV0dGVyIGlzIGEgbmV2ZXIgZW5kaW5nIHN0b3J5IHRoYXQgcmVzdGFydHMgd2l0aCBldmVyeSBuZXcgcHJvamVjdC4NCg0K5bm46L+Q55qE5piv77yM5L2c6ICF5pu+5LiO5LiA5Lqb4oCc5py65pm64oCd55qE6K6h566X56eR5a2m5a626K6o6K666L+H5aaC5L2V5o+Q6auY57yW56iL6IO95Yqb77yM5YaZ5Ye65LiT5Lia5YyW77yI6Iez5bCR5Yir5aSq5Lia5L2Z77yJ5Luj56CB55qE6Zeu6aKY44CC5LiL5paH5piv5L2c6ICF6Ieq56a75qCh5LmL5ZCO5oSf5oKf5Yiw55qE5LiA5Lqb5YWz6ZSu54K544CC6L+Z5Lqb5YWz6ZSu54K55biu5Yqp5L2c6ICF5Zyo55CG6K665ZKM5a6e6Le15Lit6Kej5Yaz5LqG5pu05Li65aSN5p2C55qE6Zeu6aKY44CC5o+Q6auY5L2g55qE57yW56iL6IO95Yqb5piv5LiA5p2h5a2m6ICM5peg5rav55qE6Lev77yM5L2g55qE5q+P5Liq5paw6aG555uu6YO95piv5LiA5qyh6ZS754K855qE6L+H56iL44CCDQoNCiMjIDEuIFBhcmFtZXRlcnMsIGNvbnN0YW50cyBhbmQgZnVuY3Rpb25zIOWPguaVsOOAgeW4uOmHj+WSjOWHveaVsA0KDQpZb3UgYXJlIGFibGUgdG8gZWFzaWx5IHJlLXVzZSB5b3VyIGNvZGUgaWYgeW91IG1ha2UgaXQgYXBwbGljYWJsZSB0byBzaW1pbGFyIHByb2JsZW1zIGFzIHdlbGwuIEEgc2ltcGxlIHdpc2RvbSB0aGF0IGlzIGhvd2V2ZXIgcXVpdGUgdHJpY2t5IHRvIGFwcGx5IGluIHByYWN0aWNlLiBZb3VyIGJ1aWxkaW5nIGJsb2NrcyBoZXJlIGFyZSAqKnBhcmFtZXRlcnMqKiwgKipjb25zdGFudHMqKiBhbmQgKipmdW5jdGlvbnMqKi4NCg0K5aaC5p6c5L2g5Zyo5YaZ5Luj56CB55qE5pe25YCZ5bCx5bey57uP6ICD6JmR6L+H55So5a6D5p2l6Kej5Yaz55u45Ly855qE6Zeu6aKY77yM6YKj5LmI5YaZ5Ye65p2l55qE5Luj56CB5piv5b6I5a655piT6KKr56e75qSN55qE44CC6L+Z5Liq5oOz5rOV6Jm954S2566A5Y2V77yM5a6e6Le16LW35p2l5Y205bm25LiN5a655piT44CC6ICMKirlj4LmlbDjgIHluLjph4/lkozlh73mlbAqKuS+v+aYr+S9oOWunueOsOi/meeCueeahOWfuuehgOOAgg0KDQpQYXJhbWV0ZXJzIGVuYWJsZSB5b3UgdG8gY2hhbmdlIGltcG9ydGFudCB2YXJpYWJsZXMgYW5kIHNldHRpbmdzIGluIG9uZSBwbGFjZS4gWW91IHNob3VsZCBuZXZlciBoYXZlIGFueXRoaW5nIGhhcmQtY29kZWQgaW4gdGhlIGJvZHkgb2YgeW91ciBjb2RlLiBDb25zdGFudHMgaGVscCB5b3UgdG8gZGVmaW5lIHN0YXRpYyB2YXJpYWJsZXMgdGhhdCBjYW5ub3QgYmUgYWx0ZXJlZC4gQ29uc3RhbnRzIGFyZSB1c2VmdWwgZm9yIGV4YW1wbGUgd2hlbiB5b3UgbmVlZCB0byBjb21wYXJlIHN0cmluZ3MuDQoNCuWPguaVsOWPr+eUqOS6juaUueWPmOmHjeimgeWPmOmHj+eahOWAvOWSjOWujOaIkOS4gOS6m+iuvue9ruOAguWGmeS7o+eggeeahOaXtuWAme+8jOazqOaEj+S4jeimgeS7peW4uOmHj+eahOW9ouW8j+WOu+iuvuWumuWPguaVsOOAguiAjOW4uOmHj+WImeWPr+S7peeUqOadpeWumuS5ieS4jeS8muaUueWPmOeahOmdmeaAgemHj++8jOS4vuS+i+adpeivtO+8jOW9k+S9oOmcgOimgeavlOi+g+Wtl+espuS4suaXtuWwseWPr+S7peeUqOWIsOW4uOmHj+OAgg0KDQoNCmBgYHtyIHBhcmFtZXRlcnMgY29uc3RhbnRzIGFuZCBmdW5jdGlvbnN9DQppbnN0YWxsLnBhY2thZ2VzKCJmdXRpbGUubG9nZ2VyIikNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGZ1dGlsZS5sb2dnZXIpDQoNCiMnIGNvbnN0YW50c+W4uOmHjw0KREFUQVNFVF9JUklTIDwtICdpcmlzJw0KREFUQVNFVF9NVENBUlMgPC0gJ210Y2FycycNCklSSVNfVEFSR0VUIDwtICdTZXBhbC5MZW5ndGgnDQpNVENBUlNfVEFSR0VUIDwtICdtcGcnDQpNT0RFTExJTkdfTUVUSE9EX1JGIDwtICdyYW5kb20gZm9yZXN0Jw0KTU9ERUxMSU5HX01FVEhPRF9HQk0gPC0gJ2dyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUnDQoNCiMnIHBhcmFtZXRlcnMg5Y+C5pWwDQpEQVRBU0VUIDwtIERBVEFTRVRfSVJJUw0KTU9ERUxMSU5HX01FVEhPRCA8LSBNT0RFTExJTkdfTUVUSE9EX0dCTQ0KDQojJyBsb2FkIGRhdGEg6L295YWl5pWw5o2uDQpmbG9nLmluZm8ocGFzdGUwKCdMb2FkaW5nICcsIERBVEFTRVQsICcgZGF0YXNldCcpKQ0KaWYgKERBVEFTRVQgPT0gREFUQVNFVF9JUklTKXsNCiAgZGF0YShpcmlzKQ0KICBkZiA8LSBpcmlzDQogIHRhcmdldF92YXJpYWJsZSA8LSBJUklTX1RBUkdFVA0KfSBlbHNlIGlmIChEQVRBU0VUID09IERBVEFTRVRfTVRDQVJTKXsNCiAgZGF0YShtdGNhcnMpDQogIGRmIDwtIG10Y2Fycw0KICB0YXJnZXRfdmFyaWFibGUgPC0gTVRDQVJTX1RBUkdFVA0KfQ0KDQojJyBjcmVhdGUgZm9ybXVsYSDmnoTlu7rooajovr7lvI8NCm1vZGVsbGluZ19mb3JtdWxhIDwtIGFzLmZvcm11bGEocGFzdGUwKHRhcmdldF92YXJpYWJsZSwgJ34uJykpDQoNCiMnIHRyYWluIG1vZGVsDQpmbG9nLmluZm8ocGFzdGUwKCdGaXR0aW5nICcsIE1PREVMTElOR19NRVRIT0QpKQ0KaWYoTU9ERUxMSU5HX01FVEhPRCA9PSBNT0RFTExJTkdfTUVUSE9EX1JGKXsNCiAgc2V0LnNlZWQoNDIpDQogIG15X21vZGVsIDwtIGNhcmV0Ojp0cmFpbihmb3JtPW1vZGVsbGluZ19mb3JtdWxhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kZiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0ncmYnKQ0KfSBlbHNlIGlmKE1PREVMTElOR19NRVRIT0QgPT0gTU9ERUxMSU5HX01FVEhPRF9HQk0pew0KICBzZXQuc2VlZCg0MikNCiAgbXlfbW9kZWwgPC0gY2FyZXQ6OnRyYWluKGZvcm09bW9kZWxsaW5nX2Zvcm11bGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPWRmLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSdnYm0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZT1GQUxTRSkNCn0NCm15X21vZGVsDQpgYGANCg0KRnVuY3Rpb25zIGFyZSBrZXkgaW5ncmVkaWVudHMgb2YgcHJvZ3JhbW1pbmcuIEFsd2F5cyBwdXQgdGhlIHJlcGV0aXRpdmUgdGFza3MgaW4geW91ciBjb2RlIGludG8gZnVuY3Rpb25zLiBUaGVzZSBmdW5jdGlvbnMgc2hvdWxkIGFsd2F5cyBhaW0gdG8gcGVyZm9ybSBvbmUgdGFzayBhbmQgYmUgZ2VuZXJhbCBlbm91Z2ggdG8gYmUgdXNlZCBmb3Igc2ltaWxhciBjYXNlcy4gSG93IGdlbmVyYWwgdHlwaWNhbGx5IGRlcGVuZHMgb24gd2hhdCB5b3Ugd2FudCB0byBhY2hpZXZlLg0KDQrlh73mlbDmmK/nvJbnqIvkuK3nmoTph43opoHkuIDnjq/vvIzmiJHku6zlvoDlvoDku6Xlh73mlbDnmoTmlrnlvI/lpITnkIbph43lpI3mgKflt6XkvZzjgILpgJrluLjmg4XlhrXkuIvvvIzlh73mlbDlupTlvZPog73lpJ/op6PlhrPmn5Dnp43nibnlrprnmoTpl67popjlubbotrPku6XlupTnlKjkuo7nm7jkvLznmoTmg4XlhrXvvIzlhbbooqvmjqjlub/nmoTnqIvluqblj5blhrPkuo7kvaDmg7Plrp7njrDku4DkuYjmoLfnmoTnm67moIfjgIINCg0KRXZlbiBoZWxwZXIgZnVuY3Rpb25zICoqc2hvdWxkKiogYmUgd2VsbCBkb2N1bWVudGVkLiBUaGUgYWJzb2x1dGUgbWluaW11bSBpcyB0byBzdW1tYXJpemUgd2hhdCB0aGUgZnVuY3Rpb24gc2hvdWxkIGRvIGFuZCB3aGF0IGlzIHRoZSBtZWFuaW5nIG9mIGlucHV0IHBhcmFtZXRlcnMuIEkgdXN1YWxseSB1c2Ugcm94eWdlbiBjb21tZW50cyBzbyB0aGF0IHRoZSBmdW5jdGlvbiBjYW4gYmUgbGF0ZXIgdXNlZCBpbiBhbiBSIHBhY2thZ2Ugd2l0aG91dCBtdWNoIGV4dHJhIHdvcmsuIEZvciBtb3JlIGRldGFpbHMgcGxlYXNlIHNlZSBbaGVyZTpodHRwOi8vci1wa2dzLmhhZC5jby5uei9tYW4uaHRtbF0oaHR0cDovL3ItcGtncy5oYWQuY28ubnovbWFuLmh0bWwpLg0KDQrlh73mlbDpnIDopoHpmYTkuIrop6Pph4ror7TmmI7pg6jliIbvvIzmnIDotbfnoIHopoHor7TmmI7lh73mlbDlj6/ku6Xop6PlhrPku4DkuYjpl67popjlkozlkITkuKrovpPlhaXlj4LmlbDnmoTmhI/kuYnjgILkvZzogIXkuIDoiKzkvb/nlKggUiDljIXmnoTlu7rml7bnmoTms6jph4rmlrnlvI/vvIzku6Xkvr/kuo7lh73mlbDlj6/ku6Xnm7TmjqXnlKjkuo4gUiDljIXkuK3vvIzogIzkuI3pnIDopoHpop3lpJbnmoTlpITnkIbjgILmm7TlpJrms6jph4rnmoTnu4boioLlj6/lnKgg4oCd5aaC5L2V57yW5YaZIFIg5YyF55qE6K+05piO5paH5qGj4oCcIOe9kemhteS4reafpeeci+OAgg0KDQpgYGB7ciByb3h5Z2VuIGNvbW1lbnRzfQ0KIycgQ2FsY3VsYXRlcyBSb290IE1lYW4gU3F1ZWFyZWQgRXJyb3Ig6K6h566X5Z2H5pa55qC56K+v5beuDQojJw0KIycgQHBhcmFtIG9ic2VydmVkIHZlY3RvciB3aXRoIG9ic2VydmVkIHZhbHVlcyDlj4LmlbDooajnpLrop4LmtYvlgLwNCiMnIEBwYXJhbSBwcmVkaWN0ZWQgdmVjdG9yIHdpdGggcHJlZGljdGVkIHZhbHVlcyDlj4LmlbDooajnpLrpooTmtYvlgLwNCiMnIEByZXR1cm4gbnVtZXJpYyDov5Tlm57mlbDlgLznsbvlnosNCmZfY2FsY3VsYXRlX3Jtc2UgPC0gZnVuY3Rpb24ob2JzZXJ2ZWQsIHByZWRpY3RlZCl7DQogIGVycm9yIDwtIG9ic2VydmVkIC0gcHJlZGljdGVkDQogIHJldHVybihyb3VuZChzcXJ0KG1lYW4oZXJyb3JeMikpLCAyKSkNCn0NCg0KYGBgDQoNCllvdSBoYXZlIHRvIHRlc3QgdGhlIGZ1bmN0aW9ucyB5b3UgYXJlIHdyaXRpbmcgYW55d2F5IHNvIGl0IGlzIGEgZ29vZCBpZGVhIHRvIGF1dG9tYXRlIHRoaXMgc3RlcCBpbiBjYXNlIHlvdSB3b3VsZCBsaWtlIHRvIHVwZGF0ZSB0aGUgZnVuY3Rpb25zIGluIHRoZSBmdXR1cmUuIFRoaXMgaXMgaW1wb3J0YW50IGVzcGVjaWFsbHkgaWYgeW91IHBsYW4gdG8gW3dyYXAgeW91IGZ1bmN0aW9ucyBpbiBhIHBhY2thZ2U6IGh0dHA6Ly9yLXBrZ3MuaGFkLmNvLm56L10oaHR0cDovL3ItcGtncy5oYWQuY28ubnovKS4gTmljZSB3YXkgdG8gZG8gdGhpcyBpcyB1c2luZyBbdGVzdHRoYXQ6aHR0cHM6Ly9naXRodWIuY29tL2hhZGxleS90ZXN0dGhhdF0oaHR0cHM6Ly9naXRodWIuY29tL2hhZGxleS90ZXN0dGhhdCkgcGFja2FnZS4gW0hlcmU6aHR0cDovL2ticm9tYW4ub3JnL3BrZ19wcmltZXIvcGFnZXMvdGVzdHMuaHRtbF0oaHR0cDovL2ticm9tYW4ub3JnL3BrZ19wcmltZXIvcGFnZXMvdGVzdHMuaHRtbCkgaXMgYSBuaWNlIHBhZ2UgaG93IHRvIHJ1biB5b3VyIHRlc3RzIGF1dG9tYXRpY2FsbHkuDQoNCuS9oOmcgOimgeajgOmqjOaJgOe8luWGmeeahOWHveaVsOaYr+WQpuiDveWkn+ato+W4uOi/kOihjO+8jOWboOatpO+8jOWwhuajgOmqjOeahOa1geeoi+iHquWKqOWMluWkhOeQhuS8muS+v+S6juaXpeWQjuWvueWHveaVsOeahOaUueWKqOOAguW9k+S9oOmcgOimgSDmnoTlu7ogUiDljIUg5pe25Yib5bu65Ye95pWw77yM6L+Z54K55bCk5YW26YeN6KaB44CCdGVzdHRoYXQg5YyF5Y+v5Lul5b6I5pa55L6/55qE6Kej5Yaz6L+Z5Liq6Zeu6aKY77yM5L2g5Y+v5Lul5ZyoIOKAnOWmguS9leajgOmqjCBSIOWMheS4reS7o+eggeeahOacieaViOaAp+KAnSDnvZHpobXkuK3nnIvliLDlpoLkvZXoh6rliqjmo4DpqozkvaDnmoTku6PnoIHjgIINCg0KYGBge3IgdGVzdHRoYXR9DQpsaWJyYXJ5KHRlc3R0aGF0KQ0KbGlicmFyeShNZXRyaWNzKQ0KDQojJyB0ZXN0aW5nIG9mIGZfY2FsY3VsYXRlX3Jtc2Ug5qOA6aqMIGZfY2FsY3VsYXRlX3Jtc2Ug5Ye95pWwDQp0ZXN0X3RoYXQoJ1Jvb3QgTWVhbiBTcXVhcmUgRXJyb3InLCB7DQogICMnIGNyZWF0ZSBzb21lIGRhdGENCiAgbiA8LSAxMDANCiAgb2JzZXJ2ZWQgPC0gcm5vcm0obikNCiAgcHJlZGljdGVkIDwtIHJub3JtKG4pDQogIG15X3Jtc2UgPC0gZl9jYWxjdWxhdGVfcm1zZShvYnNlcnZlZD1vYnNlcnZlZCwgcHJlZGljdGVkPXByZWRpY3RlZCkNCg0KICAjJyBzYW1lIHJlc3VsdHMgYXMgTWV0cmljczo6cm1zZSAg5qOA6aqM5LiOIE1ldHJpY3Mg5YyF5Lit55qEIHJtc2Ug5Ye95pWw57uT5p6c5piv5ZCm55u45ZCMIA0KICBleHBlY3RfZXF1YWwobXlfcm1zZSwgDQogICAgICAgICAgICAgICBNZXRyaWNzOjpybXNlKGFjdHVhbD1vYnNlcnZlZCwgcHJlZGljdGVkPXByZWRpY3RlZCksIA0KICAgICAgICAgICAgICAgdG9sZXJhbmNlPS4wNSkgDQogICMnIG91dHB1dCBpcyBudW1lcmljIGFuZCBub24tbmVnYXRpdmUg5qOA6aqM6L6T5Ye657uT5p6c5piv5pWw5YC857G75Z6L5LiU6Z2e6LSf5pWwDQogIGV4cGVjdF90aGF0KG15X3Jtc2UsIGlzX2EoIm51bWVyaWMiKSkgDQogIGV4cGVjdF90aGF0KG15X3Jtc2UgPj0gMCwgaXNfdHJ1ZSgpKQ0KfSkNCmBgYA0KDQpPYnZpb3VzbHkgb25lIGRvZXMgbm90IG5lZWQgdG8gd3JpdGUgYWxsIHRoZSBmdW5jdGlvbnMgbmVlZGVkLiBBIGdyZWF0IGFkdmFudGFnZSBvZiBSIGlzIHRoYXQgdGhlcmUgYXJlIHNvIG1hbnkgZnVuY3Rpb25zIGF2YWlsYWJsZSBpbiB0aG91c2FuZHMgb2YgYXZhaWxhYmxlIGxpYnJhcmllcy4gVG8gbWFrZSBzdXJlIHlvdSB3aWxsIG5vdCBydW4gaW50byBuYW1lc3BhY2UgcHJvYmxlbXMgd2hlbiB0d28gbG9hZGVkIGxpYnJhcmllcyBib3RoIGNvbnRhaW4gYSBmdW5jdGlvbiB3aXRoIHRoZSBzYW1lIG5hbWUsIHNwZWNpZnkgdGhlIHBhY2thZ2UgeW91IHdhbnQgdG8gdXNlIGJ5IGBwYWNrYWdlbmFtZTo6ZnVuY3Rpb25uYW1lKClgLiBBbiBleGFtcGxlIGlzIHRoZSBgc3VtbWFyaXNlYCBmdW5jdGlvbiB3aGVuIGJvdGggW3BseXI6aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3BseXIvaW5kZXguaHRtbF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3BseXIvaW5kZXguaHRtbCkgYW5kIFtkcGx5cjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RwbHlyL2luZGV4Lmh0bWxdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9kcGx5ci9pbmRleC5odG1sKSBwYWNrYWdlcyBhcmUgbG9hZGVkLg0KDQrlpb3lnKjmiJHku6zkuI3pnIDopoHoh6rlt7HnvJblhpnmiYDmnInnmoTlh73mlbDvvIwgUiDor63oqIDnmoTlpb3lpITlsLHlnKjkuo7lnKjlroPlpKfph4/nmoTljIXph4zlt7Lnu4/mnInlvojlpJrlj6/nlKjnmoTlh73mlbDkuobjgILkuLrkuobpgb/lhY3lh7rnjrDovb3lhaXnmoTkuKTkuKrljIXkuK3mnInnm7jlkIzlkI3np7Dlh73mlbDnmoTph43lkI3pl67popjvvIzkvaDlj6/ku6XpgJrov4cgYHBhY2thZ2VuYW1lOjpmdW5jdGlvbm5hbWUoKWAg55qE5pa55byP5oyH5a6a5oOz5L2/55So55qE5YyF5ZCN44CC5q+U5aaC6K+077yM5Zyo6L295YWl55qEIGBwbHlyYCDlkowgYGRwbHlyYCDljIXkuK3pg73lkKvmnIlgc3VtbWFyaXNlYOWHveaVsOOAgg0KDQpgYGB7ciBwYWNrYWdlcyBhbmQgZnVuY3Rpb25zfQ0KbGlicmFyeShwbHlyKQ0KbGlicmFyeShkcGx5cikNCg0KIycgbG9hZCBkYXRhIOi9veWFpeaVsOaNrg0KZGF0YShtdGNhcnMpDQoNCiMnIHNlZSB3aGF0IGhhcHBlbnMg5p+l55yL57uT5p6cDQpzdW1tYXJpc2UoZ3JvdXBfYnkobXRjYXJzLCBjeWwpLCBuPW4oKSkNCnBseXI6OnN1bW1hcmlzZShncm91cF9ieShtdGNhcnMsIGN5bCksIG49bigpKQ0KZHBseXI6OnN1bW1hcmlzZShncm91cF9ieShtdGNhcnMsIGN5bCksIG49bigpKQ0KYGBgDQoNCiMjIDIuIFN0eWxlIOagt+W8jw0KDQpZb3Ugd2lsbCBiZSByZWFkaW5nIHlvdXIgY29kZSBhZ2FpbiBpbiB0aGUgZnV0dXJlIHNvIGJlIG5pY2UgdG8geW91cnNlbGYgKGFuZCBhbnlvbmUgZWxzZSB3aG8gd2lsbCBoYXZlIHRvIHJlYWQgaXQpIGFuZCBoYXZlIGEgY29uc2lzdGVudCBjb2Rpbmcgc3R5bGUuIEEgbG90IG9mIHBlb3BsZSB1c2UgW0dvb2dsZeKAmXMgUiBzdHlsZTogaHR0cHM6Ly9nb29nbGUuZ2l0aHViLmlvL3N0eWxlZ3VpZGUvUmd1aWRlLnhtbF0oaHR0cHM6Ly9nb29nbGUuZ2l0aHViLmlvL3N0eWxlZ3VpZGUvUmd1aWRlLnhtbCkgb3IgW0hhZGxleSBXaWNraGFt4oCZcyBzdHlsZTogaHR0cDovL2Fkdi1yLmhhZC5jby5uei9TdHlsZS5odG1sXShodHRwOi8vYWR2LXIuaGFkLmNvLm56L1N0eWxlLmh0bWwpLg0KDQrkvaDku6XlkI7ov5jmmK/pnIDopoHph43mlrDpmIXor7vkvaDoh6rlt7HnmoTku6PnoIHvvIzlm6DmraTvvIzku6PnoIHpnIDopoHkv53mjIHkuIDkuKrov57otK/nmoTmoLflvI/vvIzkvr/kuo7oh6rlt7Hlkozlhbbku5bpmIXor7vnmoTkurrog73lpJ/nkIbop6PjgILlvojlpJrkurrkvb/nlKgg6LC35q2M55qEIFIg57yW56iL6KeE6IyDIOaIluaYryBIYWRsZXkgV2lja2hhbSDnmoQgUiDor63oqIDnvJbnqIvmoLflvI/jgIINCg0KSGVyZSBJIGFsc28gbmVlZCB0byBzdHJlc3MgdGhhdCBpdCBpcyBpbXBvcnRhbnQgdG8gY29tbWVudCB5b3VyIGNvZGUuIEVzcGVjaWFsbHkgd2hlbiB5b3UgY29uc2lkZXIgeW91ciBzb2x1dGlvbiBicmlsbGlhbnQgYW5kIG9idmlvdXMuIEFsc28gcGxlYXNlIGRvIG5vdCBiZSBhZnJhaWQgb2YgbG9uZyBidXQgc2VsZi1leHBsYW5hdG9yeSBmdW5jdGlvbiBhbmQgdmFyaWFibGUgbmFtZXMuDQoNCue7meS7o+eggeWKoOS4iuazqOmHiuaYr+mdnuW4uOmHjeimgeeahO+8jOeJueWIq+aYr+WcqOS9oOiupOS4uuaehOW7uuaAnei3r+W+iOWlveeahOaXtuWAmeaWueS+v+aXpeWQjuafpeeci+OAguWQjOaXtu+8jOS5n+S4jeimgeW/mOS6huWKoOS4iueci+S8vOacieS6m+WGl+mVv+eahOS9huaYr+iDveWkn+iHquaIkeino+mHiuaAp+eahOWHveaVsOWSjOWPmOmHj+WQjeOAgg0KDQoNCiMjIDMuIFZlcnNpb24gY29udHJvbCDniYjmnKzmjqfliLYNCg0KQWx3YXlzIHVzZSB2ZXJzaW9uIGNvbnRyb2wgZm9yIHlvdXIgcHJvamVjdHMuIEl0IHdpbGwgc2F2ZSB5b3UgYSBsb3Qgb2YgbmVydmVzLiBJdCBoYXMgc28gbWFueSBhZHZhbnRhZ2VzLiBbSGVyZTogaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xNDA4NDUwL3doeS1zaG91bGQtaS11c2UtdmVyc2lvbi1jb250cm9sXShodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzE0MDg0NTAvd2h5LXNob3VsZC1pLXVzZS12ZXJzaW9uLWNvbnRyb2wpaXMgYSBuaWNlIHN1bW1hcnkgb2YgdGhlbS4gVGhlIG1vc3QgaW1wb3J0YW50IHRvIG1lIGFyZToNCg0K6aG555uu566h55CG5Lit77yM54mI5pys5o6n5Yi25Lmf5b6I6YeN6KaB77yM5a6D5Y+v5Lul6IqC55yB5L2g5aSn6YeP55qE57K+5Yqb44CC54mI5pys5o6n5Yi25YW35pyJ5b6I5aSa5aW95aSE77yM5L2g5Y+v5Lul5ZyoIOKAnOS9v+eUqOeJiOacrOaOp+WItueahOWOn+WboOKAnSDnvZHpobXkuK3nnIvliLDjgILlr7nkuo7kvZzogIXogIzoqIDvvIzmnIDph43opoHnmoTlpb3lpITlnKjkuo7vvJoNCg0KLSBhYmlsaXR5IHRvIHJldmVydCBiYWNrIHRvIHByZXZpb3VzIHZlcnNpb25zIG9mIG15IGNvZGUg6IO95aSf5Zue5rqv5Yiw5LmL5YmN54mI5pys55qE5Luj56CBDQoNCi0gY2xlYW4gcHJvamVjdCBmb2xkZXIgYmVjYXVzZSBJIGNhbiBkZWxldGUgYW55dGhpbmcgd2l0aG91dCBmZWFyIOeuoeeQhumhueebruaWh+S7tu+8jOS4jeeUqOaLheW/g+ivr+aTjeS9nA0KDQotIGVhc3kgdG8gaW52aXRlIGNvbGxlYWd1ZXMgdG8gY29sbGFib3JhdGUgb24gdGhlIHByb2plY3Qg5L6/5LqO5LiO5ZCM5LqL5Y2P5L2cDQoNClVzaW5nIGdpdCBpcyBbZWFzeTogaHR0cDovL3JvZ2VyZHVkbGVyLmdpdGh1Yi5pby9naXQtZ3VpZGUvXShodHRwOi8vcm9nZXJkdWRsZXIuZ2l0aHViLmlvL2dpdC1ndWlkZS8pLiBFc3BlY2lhbGx5IGZyb20gW1JTdHVkaW86IGh0dHBzOi8vc3VwcG9ydC5yc3R1ZGlvLmNvbS9oYy9lbi11cy9hcnRpY2xlcy8yMDA1MzIwNzctVmVyc2lvbi1Db250cm9sLXdpdGgtR2l0LWFuZC1TVk5dKGh0dHBzOi8vc3VwcG9ydC5yc3R1ZGlvLmNvbS9oYy9lbi11cy9hcnRpY2xlcy8yMDA1MzIwNzctVmVyc2lvbi1Db250cm9sLXdpdGgtR2l0LWFuZC1TVk4pLg0KDQrkvaDlj6/ku6Xkvb/nlKggZ2l0IOi/m+ihjOeJiOacrOaOp+WItu+8jOeJueWIq+aYr+WcqCBSU3R1ZGlvIOS4reOAgg0KDQojIyA0LiBEZXZlbG9wbWVudCDkvJjljJblvIDlj5Hku6PnoIENCg0KRGV2ZWxvcG1lbnQgZG9lc27igJl0IG5lY2Vzc2FyaWx5IG5lZWQgdG8gYmUgYSBtZXNzeSBwcm9jZXNzLg0KDQrkvJjljJbku6PnoIHnmoTov4fnqIvkuI3kuIDlrprlsLHmmK/mnYLkubHml6Dnq6DnmoTjgIINCg0KWW91ciBjb2RlIGlzIG5vdCB3b3JraW5nPyBUaGVuIHlvdSBuZWVkIHRvIGJlIGFibGUgdG8gcXVpY2tseSBsb2NhdGUgdGhlIHByb2JsZW0gYW5kIGZpeCBpdC4gTHVja2lseSwgUlN0dWRpbyBoYXMgYSBsb3Qgb2YgW2J1aWx0LWluIGRlYnVnZ2luZyB0b29sczogaHR0cHM6Ly9zdXBwb3J0LnJzdHVkaW8uY29tL2hjL2VuLXVzL2FydGljbGVzLzIwNTYxMjYyNy1EZWJ1Z2dpbmctd2l0aC1SU3R1ZGlvXShodHRwczovL3N1cHBvcnQucnN0dWRpby5jb20vaGMvZW4tdXMvYXJ0aWNsZXMvMjA1NjEyNjI3LURlYnVnZ2luZy13aXRoLVJTdHVkaW8pIHNvIHRoYXQgeW91IGNhbiBzdG9wIHRoZSBjb2RlIGF0IHRoZSBwb2ludCB3aGVyZSB5b3Ugc3VzcGVjdCB0aGUgcHJvYmxlbSBpcyBhcmlzaW5nLCBhbmQgbG9vayBhdCBhbmQvb3Igd2FsayB0aHJvdWdoIHRoZSBjb2RlLCBzdGVwLWJ5LXN0ZXAgYXQgdGhhdCBwb2ludC4NCg0K5aaC5p6c5Luj56CB6L+Q6KGM5Ye65pWF6Zqc77yM5bCx6ZyA6KaB5b+r6YCf5a6a5L2N5Yiw6Zeu6aKY5bm26Kej5Yaz5a6D44CC5aW95ZyoIFJTdHVkaW8g5oul5pyJ5b6I5aSaIOWGhee9ruiwg+ivleW3peWFtyDvvIzkvaDlj6/ku6XlnKjorqTkuLrku6PnoIHlh7rnjrDpl67popjnmoTlnLDmlrnliJvlu7rkuIDkuKrmlq3ngrnvvIznhLblkI7ph43mlrDov5DooYzku6PnoIHvvIzph43lpI3ov5nkuKrov4fnqIvmnaXmib7lh7rpl67popjmiYDlnKjjgIINCg0KYGBge3IgRGV2ZWxvcG1lbnQgb3Igb3B0aW1pemV9DQojJyBjcmVhdGUgc29tZSBkYXRhIOWIm+W7uuaVsOaNrg0Kc2V0LnNlZWQoNDIpDQpuIDwtIDEwMA0Kb2JzZXJ2ZWQgPC0gcm5vcm0obikNCnByZWRpY3RlZCA8LSBybm9ybShuKQ0KDQojJyBkZWJ1ZyB0aGUgZnVuY3Rpb24g6LCD6K+V5Ye95pWwDQpkZWJ1ZyhmX2NhbGN1bGF0ZV9ybXNlKQ0KZl9jYWxjdWxhdGVfcm1zZShvYnNlcnZlZD1vYnNlcnZlZCwgcHJlZGljdGVkPXByZWRpY3RlZCkNCmBgYA0KDQpFYWNoIHByb2dyYW1taW5nIGxhbmd1YWdlIGhhcyBpdHMgb3duIHN0cmVuZ3RocyBhbmQgd2Vha25lc3NlcyB0aGF0IHlvdSBuZWVkIHRvIGtlZXAgaW4gbWluZC4gWW91IGRvbuKAmXQgd2FudCB5b3VyIGNvZGUgdG8gcnVuIHRvbyBzbG93IG9yIHVzZSB0b28gbXVjaCBtZW1vcnkuIEEgaGFuZHkgdG9vbCBmb3IgdGhpcyBpcyBwcm9maWxpbmcuIEFnYWluLCBSU3R1ZGlvIGNvbWVzIHdpdGggYSBbc29sdXRpb246IGh0dHBzOi8vc3VwcG9ydC5yc3R1ZGlvLmNvbS9oYy9lbi11cy9hcnRpY2xlcy8yMTgyMjE4MzctUHJvZmlsaW5nLXdpdGgtUlN0dWRpb10oaHR0cHM6Ly9zdXBwb3J0LnJzdHVkaW8uY29tL2hjL2VuLXVzL2FydGljbGVzLzIxODIyMTgzNy1Qcm9maWxpbmctd2l0aC1SU3R1ZGlvKSB0byB0aGlzLiBQcm9maWxpbmcgZW5hYmxlcyB5b3UgdG8gZGV0ZWN0IHdoZXJlIHRoZSBleGVjdXRpb24gb2YgeW91IGNvZGUgbGFzdHMgdGhlIGxvbmdlc3QgYW5kIHdoZXJlIGl0IHVzZXMgdGhlIG1vc3QgbWVtb3J5LiBEbyBub3QgcmVseSBvbiB5b3VyIGludHVpdGlvbiB3aGVuIG9wdGltaXppbmcgeW91ciBjb2RlIQ0KDQrkvaDpnIDopoHnn6XpgZPvvIzmr4/np43nvJbnqIvor63oqIDpg73mnInlroPnmoTkvJjngrnlkozkuI3otrPjgILkvaDkuZ/kuI3luIzmnJvku6PnoIHov5DooYzlvpflpKrmhaLmiJbogIXljaDnlKjlpKrlpJrlhoXlrZjjgILliIbmnpDku6PnoIHnmoTnu5PmnoTlj6/ku6XluK7liqnop6PlhrPov5nkuKrpl67popjvvIzogIwgUlN0dWRpbyDkuZ/mj5Dkvpvkuobov5nmlrnpnaLnmoQg6Kej5Yaz5pa55qGIIOOAguWIhuaekOS7o+eggee7k+aehOiDveWkn+ebkea1i+WIsOi/kOihjOS7o+eggeacgOiAl+aXtuWSjOacgOWNoOeUqOWGheWtmOeahOWcsOaWueOAguWcqOS8mOWMluS7o+eggeeahOaXtuWAmeS4jeimgei/h+W6puS+nei1luiHquW3seeahOebtOinieOAgg0KDQpZb3Ugc2hvdWxkIGFsc28gY2hlY2sgaG93IHRoZSBydW5uaW5nIHRpbWUgYW5kIG1lbW9yeSByZXF1aXJlbWVudHMgaW5jcmVhc2Ugd2l0aCB0aGUgc2l6ZSBvZiBkYXRhLiBUaGlzIHdpbGwgZ2l2ZSB5b3UgYW4gaWRlYSBmb3Igd2hhdCBkYXRhIGNhbiBiZSB5b3VyIGNvZGUgdXNlZCBhbmQgd2hhdCBjb3VsZCBiZSB0aGUgY29uc2VxdWVuY2VzIGZvciBzY2FsaW5nLg0KDQrlnKjmlbDmja7pm4blop7lpKfml7bvvIzkvaDpnIDopoHmo4Dmn6Xlhbblr7nkuo7ov5DooYzml7bpl7TlkozlhoXlrZjljaDnlKjpnIDmsYLnmoTlvbHlk43vvIzov5nngrnmnInliqnkuo7kvaDnkIbop6PlupTlvZPpgInmi6nku4DkuYjmoLfnmoTmlbDmja7pm4blkozlnKjmlbDmja7pm4bop4TmqKHmlLnlj5jml7bkvJrlvJXlj5Hku4DkuYjmoLfnmoTlkI7mnpzjgIINCg0KYGBge3IgcHJvZmlsaW5nfQ0KbGlicmFyeShwcm9mdmlzKQ0KDQojJyBwcm9maWxpbmcgb2YgZl9jYWxjdWxhdGVfcm1zZSDliIbmnpAgZl9jYWxjdWxhdGVfcm1zZSDlh73mlbDnu5PmnoQNCnByb2Z2aXMoew0KICBzZXQuc2VlZCg0MikNCiAgbiA8LSAxZTUNCiAgb2JzZXJ2ZWQgPC0gcm5vcm0obikNCiAgcHJlZGljdGVkIDwtIHJub3JtKG4pDQogIGZfY2FsY3VsYXRlX3Jtc2Uob2JzZXJ2ZWQ9b2JzZXJ2ZWQsIHByZWRpY3RlZD1wcmVkaWN0ZWQpDQp9KQ0KYGBgDQoNCkR1cmluZyB0aGUgZGV2ZWxvcG1lbnQgeW91IHdpbGwgZW5jb3VudGVyIG1hbnkgcHJvYmxlbXMuIEVhY2ggdGltZSB0aGlzIGhhcHBlbnMgeW91IHNob3VsZCBpbXByb3ZlIHRoZSBlcnJvciBoYW5kbGluZyBpbiB5b3VyIGNvZGUgYW5kIHJhaXNlIGEgc2VsZi1leHBsYW5hdG9yeSB3YXJuaW5nIG9yIGVycm9yLiBFc3BlY2lhbGx5IG1pbmQgdGhlIGRhdGEgdHlwZXMgYW5kIG1pc3NpbmcgcGFyYW1ldGVycy4NCg0K5Luj56CB5LyY5YyW6L+H56iL5Lit77yM5L2g5Lya6Z2i5a+55b6I5aSa6Zeu6aKY44CC5q+P5qyh6Zeu6aKY5Ye6546w77yM5bCk5YW26ZyA6KaB5YWz5rOo5pWw5o2u57G75Z6L5ZKM6YGX5ryP55qE5Y+C5pWw77yM5L2g5bCx5bqU5b2T6IO95aSf5LuO5Lit5o+Q5Y2H6Kej5Yaz5Luj56CB6ZSZ6K+v55qE6IO95Yqb77yM5bm26K6k6K+G5Yiw6L+Z56eN6K2m5ZGK5oiW6ZSZ6K+v55qE5oiQ5Zug44CCDQoNCiMjIDUuIERlcGxveW1lbnQg6YOo572yDQoNCkRlcGxveW1lbnQgbWVhbnMgdGhhdCB5b3VyIGNvZGUgd2lsbCBuZWVkIHRvIHJ1biBhdXRvbWF0aWNhbGx5LiBPciBhdCBsZWFzdCB3aXRob3V0IHlvdSBleGVjdXRpbmcgaXQgbGluZSBieSBsaW5lLiBJbiB0aGlzIGNhc2UgaXQgaXMgdmVyeSBoZWxwZnVsIHRvIGtub3cgd2hhdCBpcyBnb2luZyBvbiBhbmQgd2hldGhlciB0aGUgZXhlY3V0aW9uIHdlbnQgd2VsbCB3aXRob3V0IGFueSBwcm9ibGVtcy4gRm9yIHRoaXMgcHVycG9zZSBJIHVzZSBbZnV0aWxlLmxvZ2dlcjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2Z1dGlsZS5sb2dnZXIvaW5kZXguaHRtbF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2Z1dGlsZS5sb2dnZXIvaW5kZXguaHRtbCkgcGFja2FnZS4gSXQgaXMgYSBsaWdodCBzb2x1dGlvbiBhbmQgZW5hYmxlcyBtZSB0byBsb2cgdGhlIGV4ZWN1dGlvbiBvZiBteSBjb2RlcyBib3RoIHRvIHNjcmVlbiBvciBmaWxlLiBJIGp1c3QgbmVlZCB0byB3cml0ZSB1bmRlcnN0YW5kYWJsZSBtZXNzYWdlcyBpbiB0aGUgY29ycmVjdCBwbGFjZXMgaW4gbXkgY29kZS4NCg0K6YOo572y5oSP5ZGz552A5L2g55qE5Luj56CB6ZyA6KaB5a6e546w6Ieq5Yqo6L+Q6KGM55qE5Yqf6IO977yM6Iez5bCR5LiN6ZyA6KaB5L2g55qE5bmy6aKE77yM5Luj56CB5bCx5Y+v5Lul5LiA6KGM6KGM6L+Q6KGM5LiL5Y6744CC5piO56Gu5LqG6Kej5Luj56CB6L6T5Ye655qE5YaF5a655ZKM6L+Q6KGM5piv5ZCm6aG65Yip5piv6Z2e5bi46YeN6KaB55qE44CCZnV0aWxlLmxvZ2dlciDljIXlj6/ku6Xlrp7njrDku6PnoIHnmoTpg6jnvbLvvIzlroPmmK/kuIDkuKrovbvph4/nuqfop6PlhrPmlrnmoYjvvIzlubbkuJTog73lpJ/pgJrov4flsY/luZXovpPlh7rmiJblr7zlh7rmlofku7borrDlvZXkuIvku6PnoIHnmoTov5DooYzov4fnqIvjgILkvaDmiYDopoHlgZrnmoTku4Xku4XmmK/lnKjlkIjpgILnmoTkvY3nva7lhpnkuIvkvr/kuo7nkIbop6PnmoTnm7jlhbPkv6Hmga/jgIINCg0KYGBge3IgZnV0aWxlLmxvZ2dlcn0NCmxpYnJhcnkoZnV0aWxlLmxvZ2dlcikNCg0KIycgbG9nZ2luZyBzZXR1cCDlu7rnq4vorrDlvZXlmagNCmZsb2cudGhyZXNob2xkKERFQlVHKSAjIGxldmVsIG9mIGxvZ2dpbmcNCmZsb2cuYXBwZW5kZXIoYXBwZW5kZXIuZmlsZSgnZm9vLmxvZycpKSAjIGxvZyB0byBmaWxlDQoNCiMnIGxvZ2dpbmcg6K6w5b2V5Zmo55qE55u45YWz5L+h5oGvDQpmbG9nLmluZm8oJ1NvbWUgaW5mbyBtZXNzYWdlJykNCmZsb2cuZGVidWcoJ1NvbWUgZGVidWcgbWVzc2FnZScpDQpmbG9nLndhcm4oJ1NvbWUgd2FybmluZyBtZXNzYWdlJykNCmZsb2cuZXJyb3IoJ1NvbWUgZXJyb3IgbWVzc2FnZScpDQpgYGANCg0KQXV0b21hdGVkIGNvZGUgZXhlY3V0aW9uIGlzIHR5cGljYWxseSBkb25lIGJ5IENyb24gc2NoZWR1bGVyIHVzaW5nIGBSc2NyaXB0IGZvby5yYC4gVGhpcyBjb21tYW5kIHJ1bnMgdGhlIGBmb28ucmAgY29kZS4gVmVyeSBvZnRlbiB5b3Ugd2FudCB0byBzcGVjaWZ5IHNvbWUgcGFyYW1ldGVycyBvZiB0aGUgc2NyaXB0IHNvIHRoYXQgeW91IGNhbiBhbmFseXNlIGRpZmZlcmVudCBkYXRhLCBzcGVjaWZ5IHdoaWNoIG1hY2hpbmUgbGVhcm5pbmcgbWV0aG9kIHRvIHVzZSwgaWYgeW91IHdhbnQgdG8gcmV0cmFpbiB0aGUgbW9kZWwgYW5kIHNvIG9uLiBGb3IgdGhpcyBJIHVzZSB0aGUgW2FyZ3BhcnNlOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvYXJncGFyc2UvaW5kZXguaHRtbF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2FyZ3BhcnNlL2luZGV4Lmh0bWwpIHBhY2thZ2UuIEZvbGxvd2luZyBjb2RlIGVuYWJsZXMgbXkgdG8gc3BlY2lmeSB0aGUgY3N2IHdpdGggaW5wdXQgZGF0YSBpbiBjb21tYW5kIGxpbmU6IGBSc2NyaXB0IG15X2NvZGUuciAtaWYgbGF0ZXN0X2RhdGEuY3N2YC4NCg0K5Luj56CB55qE6Ieq5Yqo6L+Q6KGM6YCa5bi45Y+v5Lul5L2/55SoIENyb24g6K6h5YiS5Lu75Yqh56iL5bqP6L+Q6KGMIGZvby5yIOeahCBSIOivreiogOiEmuacrOOAgui/meS4quWRveS7pOWwhuS8mui/kOihjCBmb28uciDku6PnoIHjgILkvYblvoDlvoDkvaDpnIDopoHorr7lrprov5nkuKrohJrmnKzph4znmoTkuIDkupvlj4LmlbDmnaXliIbmnpDkuI3lkIznmoTmlbDmja7vvIzlpoLmnpzmg7Pph43mlrDorq3nu4PmqKHlnovnmoTor53ov5jpnIDopoHmjIflrprmg7Pkvb/nlKjnmoTmnLrlmajlrabkuaDmlrnms5XjgILlm6DmraTvvIzkvZzogIXkvb/nlKggYXJncGFyc2Ug5YyF77yM5LiL5YiX5Luj56CB5Y+v5Lul6L6T5YWl5ZG95LukUnNjcmlwdCBteV9jb2RlLnIgLWlmIGxhdGVzdF9kYXRhLmNzduWunueOsOmAmui/h+i+k+WFpeaWh+S7tuWQjeaMh+Wumuivu+WFpeeahCBjc3Yg5paH5Lu277yaDQoNCmBgYHtyIGF1dG8gcnVufQ0KbGlicmFyeShhcmdwYXJzZSkNCg0KIycgZGVmYXVsdCBwYXJhbWV0ZXJzIOiuvuWumum7mOiupOWPguaVsA0KSU5QVVRfRklMRV9ERUZBVUxUIDwtICdpbnB1dC5jc3YnDQoNCiMnIGNyZWF0ZSBwYXJzZXIgb2JqZWN0IOWIm+W7uuino+aekOWZqA0KcGFyc2VyIDwtIEFyZ3VtZW50UGFyc2VyKGRlc2NyaXB0aW9uPSdNeSBjb2RlJykNCg0KIycgZGVmaW5lIGFyZ3VtZW50cyDlrprkuYnlj4LmlbDnsbvnm64NCnBhcnNlciRhZGRfYXJndW1lbnQoJy1pZicsICctLWlucHV0X2ZpbGUnLCANCiAgICAgICAgICAgICAgICAgICAgZGVmYXVsdD1JTlBVVF9GSUxFX0RFRkFVTFQsIA0KICAgICAgICAgICAgICAgICAgICB0eXBlPSdjaGFyYWN0ZXInLCANCiAgICAgICAgICAgICAgICAgICAgaGVscD0nTG9jYXRpb24gb2YgY3N2IGZpbGUgd2l0aCBpbnB1dCBkYXRhJykNCg0KIycgZ2V0IGNvbW1hbmQgbGluZSBvcHRpb25zIOS8oOWFpeWRveS7pOihjA0KYXJncyA8LSBwYXJzZXIkcGFyc2VfYXJncygpDQoNCiMnIGxvYWQgZGF0YSDovb3lhaXmlbDmja4NCmRhdGEgPC0gcmVhZC5jc3YoYXJncyRpbnB1dF9maWxlKQ0KYGBgDQoNCiMjIDYuIFBsb3R0aW5nIOe7mOWbvg0KDQpEYXRhIHZpc3VhbGl6YXRpb24gaXMgdGhlIOKAnHNob3Agd2luZG934oCdIG9mIGFuYWx5dGljcy4gVGhlcmVmb3JlIHlvdSB3aWxsIHByb2JhYmx5IHNwZW5kIGEgbG90IG9mIHRpbWUgZmluZS10dW5pbmcgZWFjaCBwbG90LiBHb29kIGJlc3QgcHJhY3RpY2UgaXMgdGhlIGZvbGxvd2luZy4NCg0K5pWw5o2u5Y+v6KeG5YyW5Y+v5Lul56ew5b6X5LiK5piv5pWw5o2u5YiG5p6Q5Lit55qE4oCc5qmx56qX5p+c4oCd5LqG77yM5Zug5q2k5L2g5Y+v6IO95Lya6Iqx6LS55aSn6YeP5pe26Ze06LCD5pW05q+P5Liq6L6T5Ye65Zu+44CC5pyA5aW955qE5pON5L2c5pa55qGI5aaC5LiL77yaDQoNCjEuIGRlZmluZSBzdHlsZSwgY29sb3IgcGFsZXR0ZSBhbmQgYW55IG90aGVyIHBhcmFtZXRlcnMgaW4gYSBzZXBhcmF0ZSBzY3JpcHQg5Zyo5q+P5Liq6ISa5pys5Lit77yM5a6a5LmJ5qC35byP77yM6LCD6Imy5p2/5ZKM5YW25LuW5Y+C5pWw77ybDQoNCjIuIHdyaXRlIGEgZnVuY3Rpb24gdG8gY3JlYXRlIGEgcGxvdCBvYmplY3Qg57yW5YaZ6L6T5Ye65Zu+55qE5Ye95pWw77ybDQoNCjMuIHVzZSBhbm90aGVyIGZ1bmN0aW9uIHRvIGVpdGhlciBzaG93IHRoZSBwbG90IG9yIHNhdmUgaXQgdG8gYSBmaWxlIOS9v+eUqOWPpuS4gOS4quWHveaVsOWxleekuui+k+WHuuWbvuaIluS/neWtmOaIkOaWh+S7tuOAgg0KDQpMZXTigJlzIHNlZSBob3cgaXQgd29ya3MgaW4gdGhlIGZvbGxvd2luZyBiYXNpYyBleGFtcGxlLg0KDQrkuIvpnaLmmK/kuIDkuKrln7rnoYDnmoTlrp7kvovvvJoNCg0KYGBge3IgZ2dwbG90MiBkcmF3IHBsb3R9DQpsaWJyYXJ5KGdncGxvdDIpDQpkYXRhKGlyaXMpDQoNCiMnIHNvbWUgYmFzaWMgc3R5bGUg6K6+5a6a5Z+65pys5qC35byPDQpteV9jb2xsb3JzIDwtIGxpc3QoJ3JlZCc9JyNCMjIyMjInKQ0KDQojJyBmdW5jdGlvbiB0byBjcmVhdGUgaGlzdG9ncmFtIOe8luWGmeWIm+W7uuebtOaWueWbvueahOWHveaVsA0KZl9jcmVhdGVfaGlzdG9ncmFtIDwtIGZ1bmN0aW9uKGRmLCBjb2x1bW4pew0KICANCiAgcCA8LSBnZ3Bsb3QoZGYsIGFlc19zdHJpbmcoeD1jb2x1bW4pKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGg9LjEsIGZpbGw9bXlfY29sbG9ycyRyZWQpICsNCiAgICBnZ3RpdGxlKHBhc3RlMCgnSGlzdG9ncmFtIG9mICcsIGNvbHVtbikpDQogIA0KICByZXR1cm4ocCkNCn0NCg0KIycgY3JlYXRlIHBsb3RzICDliJvlu7rovpPlh7rlm77lr7nosaENCnNlcGFsX2xlbmd0aF9oaXN0IDwtIGZfY3JlYXRlX2hpc3RvZ3JhbShpcmlzLCAnU2VwYWwuTGVuZ3RoJykNCnNlcGFsX3dpZHRoX2hpc3QgPC0gZl9jcmVhdGVfaGlzdG9ncmFtKGlyaXMsICdTZXBhbC5XaWR0aCcpDQoNCiMnIHNob3cg5bGV56S6DQpzZXBhbF9sZW5ndGhfaGlzdA0KIycgc2F2ZSDkv53lrZgNCmdnc2F2ZSgnc2VwYWxfd2lkdGhfaGlzdC5wbmcnLCBwbG90PXNlcGFsX3dpZHRoX2hpc3QpDQpgYGANCg0KIyMgNy4gUmVwcm9kdWNpYmlsaXR5IOWkjeeOsOaApw0KDQpNYWtlIHN1cmUgeW91ciBjb2RlIGlzIHJlcHJvZHVjaWJsZS4gQmVjYXVzZSBhIGxvdCBvZiBkYXRhIHNjaWVuY2Ugc3RlcHMgaW52b2x2ZSByYW5kb20gc2FtcGxpbmcgb3Igb3B0aW1pemF0aW9uLCB3ZSBuZWVkIHRvIG1ha2Ugc3VyZSB0aGF0IHdlIGNhbiByZXBlYXQgdGhlIGNvZGUgd2l0aCB0aGUgc2FtZSByZXN1bHRzLiBUaGF0IGlzIHdoeSBpdCBpcyBjcml0aWNhbCB0byB1c2UgYHNldC5zZWVkKClgIGZ1bmN0aW9uLg0KDQrnoa7kv53kvaDnmoTku6PnoIHlj6/ku6XooqvlpI3njrDjgILnlLHkuo7lvojlpJrmlbDmja7liIbmnpDnmoTmraXpqqTkuK3ljIXlkKvpmo/mnLrnmoTlj5bmoLfmiJbkvJjljJbvvIzlm6DmraTmiJHku6zpnIDopoHnoa7kv53ph43mlrDov5DooYzku6PnoIHlj6/ku6XlvpfliLDlkIzmoLfnmoTnu5PmnpzjgILov5nkuZ/mmK/kuLrku4DkuYjmiJHku6zpnIDopoHkvb/nlKhzZXQuc2VlZCgp5Ye95pWw44CCDQoNCmBgYHtyIHNldC5zZWVkfQ0Kc2V0LnNlZWQoNDIpOyBzYW1wbGUoTEVUVEVSUywgNSkNCiMgWzFdICJYIiAiWiIgIkciICJUIiAiTyINCnNldC5zZWVkKDQyKTsgc2FtcGxlKExFVFRFUlMsIDUpDQojIFsxXSAiWCIgIloiICJHIiAiVCIgIk8iDQpzYW1wbGUoTEVUVEVSUywgNSkNCiMgWzFdICJOIiAiUyIgIkQiICJQIiAiVyINCmBgYA0KDQojIyA4LiBDb21iaW5lIHRvb2xzIOe7hOWQiOW3peWFtw0KDQpPbmNlIHlvdSBiZWNvbWUgY29uZmlkZW50IGluIFIgcHJvZ3JhbW1pbmcgeW91IHRlbmQgdG8gZG8gZXZlcnl0aGluZyBpbiBSLiBQbGVhc2UgZG8gbm90IGZvcmdldCB0aGF0IHRoZXJlIGFyZSBtYW55IG90aGVyIHRvb2xzIGF2YWlsYWJsZSBhbmQgdGhhbmtzIHRvIGNvbm5lY3RvcnMgdGhleSBjYW4gYmUgdXNlZCB0b2dldGhlciB3aXRoIFIuIEZvciBleGFtcGxlIEkgdmVyeSBvZnRlbiBjb21iaW5lIFIgd2l0aCBQeXRob24gb3IgU1FMIGRhdGFiYXNlcy4NCg0K5LiA5pem5L2g5ZyoIFIg6K+t6KiA57yW56iL5Lit5bCP5pyJ5omA5oiQ77yM5L2g5Lya5biM5pyb5bC95Y+v6IO95aSa5Zyw5L2/55SoIFIg5Lit55qE5bel5YW344CC5L2G6K+35Yir5b+Y5LqG6L+Y5pyJ5b6I5aSa5YW25LuW55qE5Y+v55So5bel5YW377yM6ICM5LiU6YCa6L+H5o6l5Y+j77yM5a6D5Lus5Y+v5Lul5LiOIFIg5a6e546w5YWx6YCa44CC5q+U5pa56K+077yM5L2c6ICF57uP5bi45bCGIFIg6K+t6KiA5ZKMIFB5dGhvbiDmiJYgU1FMIOaVsOaNruW6k+S4gOi1t+S9v+eUqOOAgg0KDQojIyA5LiBSZWZlcmVuY2UgYW5kIFR1dG9yaWFscyANCg0KIyMjIFJlZmVyZW5jZQ0KDQoqIFs4LXNpbXBsZS13YXlzLWhvdy10by1ib29zdC15b3VyLWNvZGluZy1za2lsbHMtbm90LWp1c3QtaW4tcl0oaHR0cHM6Ly9ibG9nLmFsb29rYW5hbHl0aWNzLmNvbS8yMDE3LzAzLzA4Lzgtc2ltcGxlLXdheXMtaG93LXRvLWJvb3N0LXlvdXItY29kaW5nLXNraWxscy1ub3QtanVzdC1pbi1yLykNCg0KKiBb5YWr5oub5o+Q5Y2H5L2g55qEIFIg6K+t6KiA57yW56iL6IO95YqbXShodHRwOi8vd3d3LmFmZW54aS5jb20vcG9zdC80NTgxMSkNCg0KIyMjIFR1dG9yaWFscyANCg0KVGhlcmUgYXJlIHNvIG1hbnkgdHV0b3JpYWxzIGFyb3VuZCB0aGF0IGl04oCZcyBoYXJkIHRvIHBpY2sgb25lLiBJIHBlcnNvbmFsbHkgZmluZCB0aGVzZSBib29rcyB2ZXJ5IHVzZWZ1bDoNCg0KKiBbaHR0cHM6Ly93d3cubWFubmluZy5jb20vYm9va3MvcHJhY3RpY2FsLWRhdGEtc2NpZW5jZS13aXRoLXJdKGh0dHBzOi8vd3d3Lm1hbm5pbmcuY29tL2Jvb2tzL3ByYWN0aWNhbC1kYXRhLXNjaWVuY2Utd2l0aC1yKQ0KDQoqIFtodHRwOi8vYXBwbGllZHByZWRpY3RpdmVtb2RlbGluZy5jb20vXShodHRwOi8vYXBwbGllZHByZWRpY3RpdmVtb2RlbGluZy5jb20vKQ0KDQoqIFtodHRwOi8vcjRkcy5oYWQuY28ubnovXShodHRwOi8vcjRkcy5oYWQuY28ubnovKQ0KDQoNCg0KDQoNCg==